package org.skyui.beans;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.skyui.annotations.SkyField;
import org.skyui.annotations.SkyManyToOne;
import org.skyui.annotations.SkyOneToMany;
import org.skyui.annotations.SkyOneToOne;
import org.skyui.annotations.SkyOption;
import org.skyui.annotations.SkyManyToMany;
import org.skyui.beanmanagers.BeanRelationManager;
import org.skyui.core.AccessControl;
import org.skyui.core.UIApplication;
import org.skyui.jpa.JPAManager;
import org.skyui.validators.Validators;
import org.skyui.validators.Validators.ChainedValidator;
import org.skyui.views.AbstractBeansTableView.Action;

import com.baycrypt.server.beans.commands.AbstractCommandBean;
import com.vaadin.data.Validator;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBean extends SimpleBeanInfo implements
	Serializable {

    @Transient
    private boolean inited;

    private void initIfNeeded() {
	if (inited) {
	    return;
	}
	synchronized (this) {
	    configureFromAnnotations();
	    inited = true;

	}
    }

    protected void setupTemplate() {

    }

    public void setTemplate(boolean isTemplate) {
	this.inited = false;
        this.isTemplate = isTemplate;
    }
    
    
    @Id
    @GeneratedValue
    protected String id;

    @Transient
    boolean isTemplate;

    @Temporal(TemporalType.TIMESTAMP)
    protected Date creationTime;

    public Date getCreationTime() {
	if (creationTime == null)
	    throw new Error("Null creation time");
	return creationTime;
    }

    @ManyToOne(cascade = { CascadeType.PERSIST }, optional = false)
    protected OrganizationBean organization;

    public OrganizationBean getOrganization() {
	if (this.organization == null) {
	    throw new RuntimeException("Null organization");
	}
	return this.organization;
    }

    @Transient
    private List<String[]> formFieldMappings;

    @Transient
    private List<String[]> imageMaps = new ArrayList<String[]>();

    public List<String[]> getImageMaps() {
	return imageMaps;
    };

    @Transient
    private Map<Class, BeanRelationManager> beanRelationManagers;

    @OneToMany(mappedBy = "target", cascade = { CascadeType.PERSIST,
	    CascadeType.REMOVE })
    Set<AbstractCommandBean> commands = new HashSet<AbstractCommandBean>();

    public void addCommand(AbstractCommandBean command) {
	if (command.isTemplate())
	    throw new Error("Got template bean");
	commands.add(command);
    }

    protected AbstractBean() {
	configureBeanRelations();
    }

    public void setOrganization(OrganizationBean organization) {
	if (organization == null)
	    throw new IllegalArgumentException("Null organization");
	this.organization = organization;
    }

    public void setID(String id) {
	if (id == null)
	    throw new IllegalArgumentException("Null id");
	this.id = id;
    }

    public Collection<BeanRelationManager> getOwnedBeansManagers() {
	if (this.beanRelationManagers == null)
	    return null;
	return this.beanRelationManagers.values();
    }

    private void configureBeanRelations() {
	Field[] fields = this.getClass().getDeclaredFields();
	if (fields == null)
	    throw new IllegalStateException("Null fields");
	for (Field field : fields) {
	    field.setAccessible(true);
	    if ((field.getAnnotation(SkyManyToMany.class) != null)
		    || (field.getAnnotation(SkyManyToOne.class) != null)
		    || (field.getAnnotation(SkyOneToMany.class) != null))
		configureBeanRelation(field.getName());
	}
    }

    private void configureBeanRelation(String field) {
	BeanRelationManager mg = new BeanRelationManager(field, this);
	if (this.beanRelationManagers == null)
	    this.beanRelationManagers = new LinkedHashMap<Class, BeanRelationManager>(
		    2);
	this.beanRelationManagers.put(mg.getBeanClass(), mg);

    }

    public void removeBeanRelation(AbstractBean b) {
	this.beanRelationManagers.get(b.getClass()).getCachedBeansSet()
		.remove(b);
    }

    public void addBeanRelation(AbstractBean b) {
	this.beanRelationManagers.get(b.getClass()).getCachedBeansSet().add(b);
    }

    private void configureFromAnnotations(Class cl) {
	if (formFieldMappings == null)
	    formFieldMappings = new ArrayList<String[]>();
	if (!(cl.equals(Object.class)))
	    configureFromAnnotations(cl.getSuperclass());
	Field[] fields = cl.getDeclaredFields();
	for (Field field : fields) {
	    field.setAccessible(true);
	    SkyField a1 = (SkyField) field.getAnnotation(SkyField.class);
	    if (a1 != null)
		processAnnotation(a1);
	    else {
		SkyOneToOne a2 = (SkyOneToOne) field
			.getAnnotation(SkyOneToOne.class);
		if (a2 != null)
		    processAnnotation(a2);
	    }
	}
	Method[] methods = cl.getDeclaredMethods();
	for (Method m : methods) {
	    m.setAccessible(true);
	    SkyField a1 = (SkyField) m.getAnnotation(SkyField.class);
	    if (a1 != null)
		processAnnotation(a1);
	    else {
		SkyOneToOne a2 = (SkyOneToOne) m
			.getAnnotation(SkyOneToOne.class);
		if (a2 != null)
		    processAnnotation(a2);
	    }
	}
    }

    private void addCheckBoxConrol(SelectableValue sv) {
	if (sv == null)
	    throw new IllegalArgumentException("Null checkbox control");
	this.checkBoxControls.add(sv);
    }

    private final static SelectableValue[] dummySV = new SelectableValue[] {};

    private void processAnnotation(Object ann) {

	boolean required = false;
	boolean image = false;
	boolean showInBriefView = false;
	String caption = null;
	Class[] validator = null;
	String read = null;
	String write = null;
	boolean immutable = false;
	boolean showInCreate = false;
	boolean showInView = false;
	boolean password = false;
	boolean passwordVerify = false;
	SkyOption[] options = null;
	String selectSource = null;
	String enableSource = null;
	String disableSource = null;
	String selectType = null;
	String[] enable = null;
	String[] disable = null;
	boolean show = false;
	String[] roles = null;
	String[] productEditions = null;
	String[] productVersions = null;

	if (ann == null) {
	    throw new IllegalArgumentException("Null annotation");
	}

	if (ann instanceof SkyField) {

	    SkyField a = (SkyField) ann;

	    required = a.required();
	    image = a.image();
	    showInBriefView = a.showInBriefView();
	    caption = a.caption();
	    validator = a.validator();
	    read = a.read();
	    write = a.write();
	    immutable = a.immutable();
	    showInCreate = a.showInCreate();
	    showInView = a.showInView();
	    password = a.password();
	    passwordVerify = a.passwordVerify();
	    options = a.options();
	    selectSource = a.selectSource();
	    enableSource = a.enableSource();
	    disableSource = a.disableSource();
	    selectType = a.selectType();
	    enable = a.enable();
	    disable = a.disable();
	    show = a.show();
	    roles = a.roles();
	    productEditions = a.productEditions();
	    productVersions = a.productVersions();
	} else if (ann instanceof SkyOneToOne) {

	    SkyOneToOne a = (SkyOneToOne) ann;

	    required = a.required();
	    image = a.image();
	    showInBriefView = a.showInBriefView();
	    caption = a.caption();
	    validator = a.validator();
	    read = a.read();
	    write = a.write();
	    immutable = a.immutable();
	    showInCreate = a.showInCreate();
	    showInView = a.showInView();
	    password = a.password();
	    passwordVerify = a.passwordVerify();
	    options = a.options();
	    selectSource = a.selectSource();
	    enableSource = a.enableSource();
	    disableSource = a.disableSource();
	    selectType = a.selectType();
	    enable = a.enable();
	    disable = a.disable();
	    show = a.show();
	    roles = a.roles();
	    productEditions = a.productEditions();
	    productVersions = a.productVersions();
	}

	if (ann != null) {
	    if (!image) {
		String[] map = new String[3];
		map[0] = caption;
		map[1] = read;
		map[2] = write;
		if ("".equals(map[1]))
		    map[1] = null;
		if ("".equals(map[2]))
		    map[2] = null;

		formFieldMappings.add(map);
	    }

	    if (this.isTemplate()) {
		if (!showInCreate) {
		    addDontShow(caption);

		    return;
		}
	    } else {
		if (!showInView) {
		    addDontShow(caption);
		    addDontShowInBriefView(caption);
		    return;
		}
	    }
	    if (required)
		setRequired(caption);
	    Class[] vs = validator;
	    if (vs != null && vs.length > 0) {
		ChainedValidator cv = new ChainedValidator();
		for (Class vcl : vs) {
		    try {
			Validator val = (Validator) vcl.newInstance();
			if ((val instanceof Validators.NewUser)
				&& !(this.isTemplate())) {

			} else {
			    cv.add(val);
			}
		    } catch (Exception e) {
			throw new Error(e);
		    }
		}
		addValidator(caption, cv);
	    }
	    if (!showInBriefView) {
		addDontShowInBriefView(caption);
	    }

	    if (image) {
		String[] map = new String[3];
		map[0] = caption;
		map[1] = read;
		if (immutable && !(this.isTemplate()))
		    map[2] = null;
		else
		    map[2] = write;
		if ("".equals(map[1]))
		    map[1] = null;
		if ("".equals(map[2]))
		    map[2] = null;
		imageMaps.add(map);
	    } else {
		if (immutable && !(this.isTemplate()))
		    this.setReadonly(caption);
	    }
	    if (password)
		this.passwordFieldID1 = caption;
	    if (passwordVerify)
		this.passwordFieldID2 = caption;
	    if ((options.length > 0 || !selectSource.equals(""))
		    && (!immutable || (this.isTemplate()))) {
		List<SelectableValue> svs = new ArrayList<SelectableValue>();
		for (SkyOption op : options) {
		    svs.add(new SelectableValue(op.value(), op.enable(), op
			    .disable()));
		}
		if (!selectSource.equals("")) {
		    try {
			Method m = this.getClass().getMethod(selectSource);
			String[] selectables = (String[]) m.invoke(this);
			for (String s : selectables) {
			    svs.add(new SelectableValue(s, null, null));
			}
		    } catch (Exception e) {
			throw new RuntimeException(e);
		    }
		}
		addSelectable(caption, svs.toArray(dummySV), selectType);
	    }
	    String[] enables = enable;
	    if (enables.length == 0)
		enables = null;
	    String[] disables = disable;
	    if (disables.length == 0)
		disables = null;
	    if ((enables != null) || disables != null) {
		this.addCheckBoxConrol(new SelectableValue(caption, enables,
			disables));
	    }
	}
    }

    private void configureFromAnnotations() {
	configureFromAnnotations(this.getClass());
    }

    protected List<String[]> getFormFieldMappings() {
	initIfNeeded();
	return this.formFieldMappings;
    }

    @Transient
    private Vector<String> order;

    @Transient
    private Vector<String> tableOrder;

    @Transient
    private Set<String> isRequiredSet;

    @Transient
    private Set<String> isReadOnlySet;

    @Transient
    private Set<String> dontShowSet;

    public Set<String> getDontShowSet() {
	initIfNeeded();
	return dontShowSet;
    }

    private void addDontShow(String caption) {
	if (dontShowSet == null)
	    dontShowSet = new HashSet<String>();
	dontShowSet.add(caption);
    }

    @Transient
    private Map<String, SelectableValue[]> selectablesMap;
    @Transient
    private Map<String, String> selectableLooksMap;
    @Transient
    private Map<String, Validator> validatorsMap;

    public synchronized Vector<String> getOrder() {
	initIfNeeded();
	if (this.order != null)
	    return this.order;
	this.order = new Vector<String>();
	List<String[]> mappings = getFormFieldMappings();
	for (String[] mapping : mappings) {
	    this.order.add(mapping[0]);
	}
	return this.order;
    }

    public synchronized Vector<String> getTableOrder() {
	initIfNeeded();
	if (this.tableOrder != null)
	    return this.tableOrder;
	this.tableOrder = new Vector<String>();
	for (String id : getOrder()) {
	    if (this.isDontShowInBriefView(id))
		continue;
	    this.tableOrder.add(id);
	}
	return this.tableOrder;
    }

    public void persist() {
	initIfNeeded();
	JPAManager.persist(this);
    }

    public void destroyPersistentBean() {
	initIfNeeded();
	JPAManager.remove(this);
    }

    public synchronized Set<String> getIsRequiredSet() {
	initIfNeeded();
	return this.isRequiredSet;
    }

    public synchronized Set<String> getIsReadOnlySet() {
	initIfNeeded();
	return this.isReadOnlySet;
    }

    public Map<String, SelectableValue[]> getSelectablesMap() {
	initIfNeeded();
	return this.selectablesMap;
    }

    public Map<String, Validator> getValidatorsMap() {
	initIfNeeded();
	return this.validatorsMap;
    }

    private void addSelectable(String key, String[] strings,
	    String selectableLook) {
	if (key == null)
	    throw new IllegalArgumentException("Null key");
	if (strings == null)
	    throw new IllegalArgumentException("Null strings");
	if (selectableLook == null)
	    throw new IllegalArgumentException("Null selectable look");
	SelectableValue[] values = new SelectableValue[strings.length];
	for (int i = 0; i < strings.length; i++) {
	    values[i] = new SelectableValue(strings[i]);
	}
	addSelectable(key, values, selectableLook);
    }

    private void addSelectable(String key, SelectableValue[] values,
	    String selectableLook) {

	if (key == null)
	    throw new IllegalArgumentException("Null key");
	if (values == null)
	    throw new IllegalArgumentException("Null values");
	if (selectableLook == null)
	    throw new IllegalArgumentException("Null selectable look");

	if (this.selectablesMap == null)
	    this.selectablesMap = new HashMap<String, SelectableValue[]>();
	if (this.selectableLooksMap == null)
	    this.selectableLooksMap = new HashMap<String, String>();
	this.selectablesMap.put(key, values);
	this.selectableLooksMap.put(key, selectableLook);

    }

    public String getId() {
	return this.id;
    }

    public Map<String, String> getSelectableLooksMap() {
	initIfNeeded();
	return this.selectableLooksMap;
    }

    public static PropertyDescriptor[] getDummy() {
	return dummy;
    }

    private void addValidator(String key, Validator validator) {

	if (key == null)
	    throw new IllegalArgumentException("Null key");
	if (validator == null)
	    throw new IllegalArgumentException("Null validator");

	if (this.validatorsMap == null)
	    this.validatorsMap = new HashMap<String, Validator>();
	this.validatorsMap.put(key, validator);
    }

    private void setRequired(String key) {
	if (key == null)
	    throw new IllegalArgumentException("Null key");
	if (this.isRequiredSet == null)
	    this.isRequiredSet = new HashSet<String>();
	this.isRequiredSet.add(key);
    }

    public void setReadonly(String key) {
	if (key == null)
	    throw new IllegalArgumentException("Null key");
	if (this.isReadOnlySet == null)
	    this.isReadOnlySet = new HashSet<String>();
	this.isReadOnlySet.add(key);
    }

    private static PropertyDescriptor[] dummy = new PropertyDescriptor[0];

    @Override
    public PropertyDescriptor[] getPropertyDescriptors() {
	initIfNeeded();
	List<String[]> mappings = getFormFieldMappings();
	List<PropertyDescriptor> descriptors = new ArrayList<PropertyDescriptor>();
	try {
	    for (String[] mapping : mappings) {
		descriptors.add(new PropertyDescriptor(mapping[0], this
			.getClass(), mapping[1], mapping[2]));
	    }
	    ;
	    return descriptors.toArray(dummy);
	} catch (IntrospectionException e) {
	    throw new Error(e);
	}
    }

    @Transient
    private List<SelectableValue> checkBoxControls = new ArrayList<SelectableValue>();

    public List<SelectableValue> getCheckBoxControls() {
	initIfNeeded();
	return this.checkBoxControls;
    }

    @Transient
    private Set<String> dontShowInBriefView = new HashSet<String>();

    @Transient
    private Set<String> readOnly = new HashSet<String>();

    private void addDontShowInBriefView(String id) {
	this.dontShowInBriefView.add(id);
    }

    public boolean isDontShowInBriefView(String id) {
	initIfNeeded();
	return this.dontShowInBriefView.contains(id);
    }

    public Set<String> getDontShowInBriefViewSet() {
	initIfNeeded();
	return this.dontShowInBriefView;
    }

    public void refresh() {
	initIfNeeded();
	JPAManager.refresh(this);
    }

    @Transient
    protected String itemsCaption;
    @Transient
    protected String selectedItemCaption;
    @Transient
    protected String addItemCaption;
    @Transient
    protected String[] buttonNames;
    @Transient
    protected Action[] buttonActions;

    public String getItemsCaption() {
	initIfNeeded();
	return this.itemsCaption;
    }

    public String getSelectedItemCaption() {
	initIfNeeded();
	return this.selectedItemCaption;
    }

    public String getAddItemCaption() {
	initIfNeeded();
	return this.addItemCaption;
    }

    public String[] getButtonNames() {
	initIfNeeded();
	return this.buttonNames;
    }

    public Action[] getButtonActions() {
	initIfNeeded();
	return this.buttonActions;
    }

    public String getPrimaryAttribute() {
	initIfNeeded();
	return getId();
    }

    public void runBeforePersist() {
	// TODO Auto-generated method stub
    }

    public void runAfterPersist() {
	// TODO Auto-generated method stub
    }

    public void createPersist() {
	this.setCreationTime(new Date());
	runBeforePersist();
	persist();
	runAfterPersist();
    }

    public void setCreationTime(Date date) {
	if (date == null)
	    throw new IllegalArgumentException("Null date");
	this.creationTime = date;
    }

    public static class SelectableValue {
	public SelectableValue(String value, String[] positiveControls,
		String[] negativeControls) {
	    if (value == null)
		throw new IllegalArgumentException("Null value");
	    this.value = value;
	    this.positiveControls = positiveControls;
	    this.negativeControls = negativeControls;
	}

	public String value;
	public String[] positiveControls;
	public String[] negativeControls;

	public SelectableValue(String value) {
	    this(value, null, null);
	}

    }

    public String getPasswordField1ID() {
	initIfNeeded();
	return this.passwordFieldID1;
    }

    public String getPasswordField2ID() {
	initIfNeeded();
	return this.passwordFieldID2;
    }

    public void setPasswordFieldIDs(String passwordFieldID1,
	    String passwordFieldID2) {
	this.passwordFieldID1 = passwordFieldID1;
	this.passwordFieldID2 = passwordFieldID2;
    }

    @Transient
    private String passwordFieldID1;
    @Transient
    private String passwordFieldID2;

    public String getDownloadLinkDescription() {
	initIfNeeded();
	throw new Error("Not implemented");
    }

    public String getDownloadFileName() {
	initIfNeeded();
	throw new Error("Not implemented");
    }

    public int parseTime(String time) {
	if (!Character.isDigit(time.charAt(0)))
	    return 0;
	time = time.toLowerCase();
	StringTokenizer st = new StringTokenizer(time);
	int timeSec = Integer.parseInt(st.nextToken()) * 60;
	String unit = st.nextToken();
	if (unit.startsWith("min")) {
	    return timeSec;
	}
	timeSec *= 60;
	if (unit.startsWith("hour")) {
	    return timeSec;
	}
	timeSec *= 24;
	if (unit.startsWith("day")) {
	    return timeSec;
	}
	timeSec *= 7;
	if (unit.startsWith("week")) {
	    return timeSec;
	}
	return 0;
    }

    public boolean isTemplate() {
	return this.isTemplate;
    }

    private static final Class[] bool = new Class[] { boolean.class };

    public static AbstractBean createTemplate(Class templateClass) {
	Constructor c;
	try {
	    AbstractBean b = (AbstractBean) templateClass.newInstance();
	    b.isTemplate = true;
	    b.initIfNeeded();
	    b.setupTemplate();
	    return b;
	} catch (Exception e) {
	    e.printStackTrace();
	    throw new RuntimeException(e);
	}
    }
    
    public void runOnCreateFromTemplate(){};
}